package org.NooLab.glue.net;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Observer;
import java.util.Vector;

import org.NooLab.glue.components.*;
import org.NooLab.glue.components.pkg.DataDeliveryPackageIntf;


import org.NooLab.glue.net.sockets.tcp.*;
import org.NooLab.glue.net.sockets.udp.*;
 

import org.NooLab.glue.net.ftp.* ;
import org.NooLab.glue.net.http.rest.*;



import org.NooLab.utilities.logging.PrintLog;
import org.NooLab.utilities.net.connex.CheckIp;
import org.NooLab.utilities.net.pages.WebRetriever;


/**
 * 
 * options 
 * - Netty : http://www.jboss.org/netty/documentation
 * - Apache http server...
 * - NooLabxmlrpc (uses apache http-c)
 * - basic simple one : using serversocket from example
 *                      http://ndm.wikidot.com/cnsoltcpmultithreading
 * 
 *  Jetty :
 *  Jigsaw: http://www.w3.org/Jigsaw/Doc/Programmer/samples/Overview.html
 *  
 * 
 */
public class ComBoxFactory implements TransportsInformerIntf{

	// ================================= 
	
	// constants ......................
	
	public static int DRS_RESTLET = 1 ;
	public static int DRS_JETTY   = 2 ;
	
	public static int DRS_FTP     = 5 ;
	
	public static int DRS_TCP     = 7 ; // very similar to UDP, just that it is TCP
	public static int DRS_SUDP    = 8 ; // simple UDP ..not appropriate for larger amounts of exchanged data
	public static int DRS_MUDP    = 9 ; // multi-threaded UDP
	
	int requestedTransport = -1;
	
	// object references ..............
	
	MessageBoardFactoryProperties factoryProperties;
	MessageBoardTrafficMonitorIntf trafficMonitor ;
	
	DataReceiverIntf receiver = null;
	DataSenderIntf   sender   = null;
	
	ComBox comBox;
	int serverState = 0;
	
	// transport protocols
	
	UdpBox udpBox ;
	TcpBox tcpBox;
	
	RestletBox restletBox ;
	RestletBoxPropertiesIntf restletBoxProperties;
	
	TcpBoxxer tcpBoxxer ;
	
	FtpBoxxer ftpBoxxer ; // boxxers are necessary as an additional layer only for single port transports
	 

	int desiredDRSLayer = 0 ;
	
	
	// main variables / properties ....
  	
	int receptorListenPort, sourceListenPort, registrationPort ;
	int ctrlPort ; 
	Observer observer ; 
	String description = "";
	int protocolID ;
	int secondaryProtocolID;
	String role;
	
	Vector<DataReceiverIntf> receivers = new Vector<DataReceiverIntf>() ;
	
	// volatile variables .............

	
	
	// helper objects .................
	public PrintLog out = new PrintLog(2,true);
	
	
	// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
	public ComBoxFactory(){
		
	} 
	
	public ComBoxFactory( MessageBoardFactoryProperties factoryproperties){
		
		factoryProperties = factoryproperties;
		
		receptorListenPort  = factoryproperties.getMsgPortforReceptors() ; 
		sourceListenPort    = factoryproperties.getMsgPortforSources();
		registrationPort	= factoryproperties.getMsgPortforRegistration() ;
		
		ctrlPort    = factoryproperties.getServicePort() ; 
		observer    = factoryproperties.getObservingObj() ; 
		description = factoryproperties.getDescription() ;
		
		if (factoryProperties.getUsePrimarySettings()<=1){  
			protocolID  = factoryproperties.getProtocolId() ;
			// this.desiredDRSLayer ??? translated ?
		}else{
			if (factoryproperties.getSecondaryProtocolId() >0){
				protocolID  = factoryproperties.getSecondaryProtocolId() ;
			}else{
				role="";
				return;
			}
		}
		
		//secondaryProtocolID = factoryProperties.getSecondaryProtocolId() ;
		
		
		translateProtocolTransportIDs( protocolID ) ;
		 
		setReceptorListenPort( factoryProperties.getMsgPortforReceptors() ) ;
		
		int fm = factoryProperties.getFactoryMode() ;
		role = factoryproperties.getParticipantsRole() ;
	}
	
	
	// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
	
	public ComBoxIntf createComBox( int listenport , int ctrlport, int protocolid, Observer observer, String description){
		
		
		receptorListenPort = listenport ; 
		ctrlPort = ctrlport ;
		protocolID = protocolid;
		this.observer = observer ;
		this.description = description ;
		
		return createDRS( listenport, ctrlport, observer, description) ;
	}
	
	
	public ComBoxIntf createComBox() {
		  
		return createDRS( receptorListenPort, ctrlPort, observer, description) ;
		 
	}
	
	public ComBox getComBox() {
		if (comBox==null){
			comBox = new ComBox(this) ;
		}
		return  comBox;
	}

	public void __setDesiredTransport( int protocolid ){
	/*
	 	public static int protocolUDP  = 1;
		public static int protocolFTP  = 2;
		public static int protocolTCP  = 3;
		public static int protocolHTTP = 4;	
	 */
		// translate into "DesiredInfraStructure"
		
		
	}
	
	public void setDesiredInfraStructure( int identifier){
		
		desiredDRSLayer = identifier;
		
	}
	
 
	// this observer links back to "update()" of org.NooLab.glue.MessageBoard
	private ComBoxIntf createDRS( int listenport , int ctrlport, Observer observer, String description ){
		
		comBox = new ComBox(this) ;
		
		if (( desiredDRSLayer== DRS_SUDP) || ( desiredDRSLayer== DRS_MUDP)){    // 8, 9
			// TODO: this should be access via the UdpBox, which is kind of a factory 
			if (udpBox==null){
				udpBox = new UdpBox( listenport , ctrlport, observer, description );
 			}
			receiver = udpBox.createReceiver(listenport , ctrlport, observer, description); 
			receivers.add(receiver);	
		}
 		
		if ( desiredDRSLayer== DRS_TCP){  // 7
			
			if (tcpBox == null){
				tcpBox = new TcpBox();
				// BoxDataIntf = createTcpDataContainer() ;
			}
			receiver = tcpBox.createReceiver( listenport , ctrlport, observer, description );
			receivers.add(receiver);
		}
		
		if ( desiredDRSLayer== DRS_FTP){ // 5

			ftpBoxxer = new FtpBoxxer() ;
			// transfer properties
			
			ftpBoxxer.getServerRunning() ;
		}

		if ( desiredDRSLayer== DRS_RESTLET){ // 1
			
			
			
			if (restletBox==null){
				restletBox = new RestletBox( this ,out);
			}
			
			restletBoxProperties = restletBox.createRestBoxProperties() ;
			// define values across the boundary
			
			
			
			restletBoxProperties.setListeningPort( factoryProperties.getMsgPortAllInOne() ) ;
			restletBoxProperties.setPortAliasForSources( factoryProperties.getMsgPortforSources() );
			restletBoxProperties.setPortAliasForReceptors( factoryProperties.getMsgPortforReceptors() );
			restletBoxProperties.setPortAliasForIBoard( factoryProperties.getMsgPortforInterBoard() );
			
			
			restletBoxProperties.setServerAddress( factoryProperties.getRemoteIpAddress() ) ;
			
			// finally create the server as receiver... in case of restlets, we will never need another one...
			receiver = restletBox.createServerAsReceiver( restletBoxProperties, observer ); 
			
			sender = restletBox.createInternalSender( factoryProperties.getMsgPortAllInOne(), 0, description) ;
		}

		
		
		// making the sender/receiver available through the interface (abstracted receiver/sender)
		comBox.setReceiver(receiver) ;		
		comBox.setSender(sender) ;
		
		
		return (ComBoxIntf)comBox;
	}

	
	synchronized protected DataSenderIntf createSender(int port, int serviceport, String description){
		
		DataSenderIntf csender = null;
		requestedTransport = -1;
		boolean isBoard;
		String str;
		
		if (( desiredDRSLayer== DRS_SUDP) || ( desiredDRSLayer== DRS_MUDP)){
			// use the udpBox for that
			if (udpBox==null){
				
			}
			csender = new UdpDataSender( port , serviceport ) ; 
			requestedTransport = 1;
		}

		if ( desiredDRSLayer== DRS_TCP){
			
			if (tcpBox==null){
				tcpBox = new TcpBox();
			}
			
			csender = tcpBox.createSender( port , serviceport, description ) ;
			requestedTransport = 1;
		}
		
		if ( desiredDRSLayer== DRS_FTP){
			
		}
		
		if ( desiredDRSLayer== DRS_RESTLET){
			
			// NOT creating a new one in restlet - server mode
			isBoard = (factoryProperties!=null) && 
					  (factoryProperties.getFactoryMode() == MessageBoardFactoryProperties.__MESSAGEBOARD);
			
			if (isBoard){ this.observer=null;
				csender = sender ; // we already created it !!! it works :))
			}else{
				 
				// obsolete ??? restletBox.setRemoteHostAddress();
				// should not be necessary ... we provided it through RestletBoxPropertiesIntf restletBoxProperties ...
				str = "localhost";
				if (restletBoxProperties!=null){
					str = restletBoxProperties.getServerAddress() ;
				}else{
					str =  factoryProperties.getRemoteIpAddress() ;
				}
				restletBox.setRemoteHostAddress(str) ;
				csender = restletBox.createSender( port , serviceport, description  ) ;
				str = str+"" ;
			}
		}
		
		// sender = csender; // ??? udp, tcp still working with that ?
		return csender ;
	}
	
	
	public DataReceiverIntf createReceiver( int listenport , int ctrlport, Observer observer, String description ) throws Exception{
		
		//DataReceiverIntf receiver = null;
		boolean isBoard;
		
		requestedTransport = -1;
		desiredDRSLayer = factoryProperties.getProtocolId() ;
		
		if (( desiredDRSLayer== DRS_SUDP) || ( desiredDRSLayer== DRS_MUDP)){
			
			receiver = new UdpDataReceiver( listenport, ctrlport, observer, description ); 
			requestedTransport = 1;
			if (receiver.getListenPort()<0){
				requestedTransport = -3;
			}
		}
	
		if ( desiredDRSLayer== DRS_TCP){
			
			if (tcpBox==null)
			{
				tcpBox = new TcpBox();
			}
			
			receiver = tcpBox.createReceiver(  listenport, ctrlport, observer, description ) ;
			if (receiver!=null){
				tcpBox.setTransportsInformer( factoryProperties.getTransportsInformer() ) ; // TransportsInformerIntf
				requestedTransport = 1;
			}else{
				requestedTransport = -3;
			}
		}
		
		if ( desiredDRSLayer== DRS_FTP){
			/*
			 * for ftp, we may run different ports as we do for UDP;
			 * we also should create a transport across a single port -> in this case we have to
			 * include an extra identifier, which resemble to the different ports = virtual port
			 */
			requestedTransport = 0;
		}
		
		if ( desiredDRSLayer== DRS_RESTLET){
			/*
			 * in the case of restlet we have only 1 physical receiver = the Restlet framework 
			 * a particular receiver accessing the restlet framework is an instance listening to
			 * a particular route  
			 * these routes are named like port numbers, i.e. (for default values) we have
			 *  <host>/7100 for listening to sources,  <host>/7200 for listening to receptors,
			 *  <host>/7112 for listening to registrations 
			 *  
			 * usually, TRUE restlets are not based on full-duplex, with servers on both sides,
			 * only the MesageBoard will actually be a http-server. 
			 * This requires continuous polling  
			 *  
			 * The receiver class we will create for the restlet framework is just a logical receiver,
			 * which docks to a particular route
			 * 
			 */
			
			// for participants (=clients) we create a 
			if (restletBox==null){
				RestBoxDataIntf restdata = null;
				restletBox = new RestletBox( this,out );
			}
			
			restletBox.setConnectionResponseTimeoutValue( factoryProperties.getConnectionTimeOut()) ;
			isBoard = (factoryProperties.getFactoryMode() == MessageBoardFactoryProperties.__MESSAGEBOARD);
			
			if (isBoard){
				// we create a http-server behind the facade of a receiver
				// this happens elsewhere
				
				 
			}else{
				/*
				when preparing clients, this "restletBoxProperties" is null...
				restletBoxProperties.setListeningPort( factoryProperties.getMsgPortAllInOne() ) ;
				restletBoxProperties.setPortAliasForSources( factoryProperties.getMsgPortforSources() );
				restletBoxProperties.setPortAliasForReceptors( factoryProperties.getMsgPortforReceptors() );
				restletBoxProperties.setPortAliasForIBoard( factoryProperties.getMsgPortforInterBoard() );
				// ??? 
				// restletBox.setproperties( restletBoxProperties ) ;
				*/
				listenport = factoryProperties.getMsgPortAllInOne() ;
				receiver = restletBox.createReceiver( listenport, ctrlport, observer, description ) ;
				
			}
			if (receiver!=null){
				requestedTransport = 1;
			}else{
				requestedTransport = -3;
			}
		}
	
		if ( requestedTransport == -3 ){
			throw(new Exception("Receiver could not be activated in the ComBoxFactory.")) ;
		}
		if ( requestedTransport == -1){
			throw(new Exception("Requested protocol for transport is unknown.")) ;
		}
		
		return receiver ;
	}

	public DataReceiverIntf getReceiver(){
		return receiver;
	}
	public void setReceiver( DataReceiverIntf  recv){
		receiver = recv;
	}
	
	public void removeReceiver( DataReceiverIntf recv){
		int n;
		
		if ( recv==null){
			return;
		}
		n = this.receivers.size() ;
		for (int i=n-1;i>0;i--){
			if (receivers.get(i)==recv){
				receivers.remove(i);
			}
		}

		if ((this.receiver == recv) && (recv!=null)){
			
			receiver.closePort();
			receiver.deleteObservers() ;
			receiver = null;
		}
	}
	
	
	public void setObserver(Observer observer) {
		 
		this.observer = observer ;
		
		if ( desiredDRSLayer== DRS_RESTLET){
			if (restletBox!=null){
				restletBox.setObserver(observer) ;
			}
		}
	}

 
	public Observer getObserver() {
		return observer;
	}

	
	public void setTrafficMonitor( MessageBoardTrafficMonitorIntf trafficmonitor){
		trafficMonitor = trafficmonitor;
	}
	
	public MessageBoardTrafficMonitorIntf getTrafficMonitor() {
		return trafficMonitor;
	}

	public void stopAll(){
		DataReceiverIntf receiver;
		
		desiredDRSLayer = factoryProperties.getProtocolId() ;
		
		if (( desiredDRSLayer== DRS_SUDP) || ( desiredDRSLayer== DRS_MUDP)){
			
		}
		
		if ( desiredDRSLayer== DRS_TCP){
			
			for (int i=0;i<receivers.size();i++){
				receiver = receivers.get(i);
				if (receiver!=null){
					receiver.closePort() ;
				}
			}
			tcpBox.deleteObservers();
			tcpBox = new TcpBox();
		}
		
		if ( desiredDRSLayer== DRS_FTP){
			
			
		}
		
		
		if ( desiredDRSLayer== DRS_RESTLET){
			
		}
	}

	public int testAssignedPort() {
		 
		return 0;
	}

	 
	public void closePort() {
		 
		
	}

 
 

	public int getCtrlPort() {
		return ctrlPort;
	}


	public void setCtrlPort(int ctrlport) {
		this.ctrlPort = ctrlport;
	}


	public String getDescription() {
		return description;
	}


	public void setDescription(String description) {
		this.description = description;
	}


	public int getProtocolID() {
		return protocolID;
	}

	 
	private void translateProtocolTransportIDs(int protocolid){
		
		/*
		  	protocolHTTP  = 1; // using the Restlet framework
			protocolHREST = 1; // ./. 
			protocolHTTPC = 4; // simple and explicit clients (not available)
			protocolFTP   = 5; // not available natively, probably through the Restlet framework
			protocolTCP   = 7;
			protocolUDP   = 9;
		  
		  	DRS_RESTLET = 1 ;
		  	DRS_JETTY   = 2 ;
		  	DRS_FTP     = 5 ;
		  	DRS_TCP     = 7 ;
		  	DRS_SUDP    = 8 ; // simple UDP ..not appropriate for larger amounts of exchanged data
		  	DRS_MUDP    = 9 ; // multi-threaded UDP
		  
		*/
		 
		if (protocolid==1) setDesiredInfraStructure( 1 ) ; 
		if (protocolid==4) setDesiredInfraStructure( 1 ) ;
		if (protocolid==5) setDesiredInfraStructure( 3 ) ;
		if (protocolid==7) setDesiredInfraStructure( 7 ) ;
		if (protocolid==9) setDesiredInfraStructure( 9 ) ;
		
	}
	
	public void setProtocolID(int protocolid) {
		this.protocolID = protocolid;
		
		translateProtocolTransportIDs(protocolID);
		
	}


	public int getSecondaryProtocolID() {
		return secondaryProtocolID;
	}

	public void setSecondaryProtocolID(int secondaryProtocolID) {
		this.secondaryProtocolID = secondaryProtocolID;
	}
	
	public int getDesiredDRSLayer() {
		return desiredDRSLayer;
	}

	public void setDesiredDRSLayer(int desiredDRSLayer) {
		this.desiredDRSLayer = desiredDRSLayer;
	}

	public int getDRSLayer() {
		return desiredDRSLayer;
	}
	
	public String getRole() {
		return role;
	}
	public void setRole( String role) {
		this.role = role;
	}
	public int getReceptorListenPort() {
		return receptorListenPort;
	}

	public void setReceptorListenPort(int receptorListenPort) {
		this.receptorListenPort = receptorListenPort;
	}

	public int getSourceListenPort() {
		return sourceListenPort;
	}

	public void setSourceListenPort(int sourceListenPort) {
		this.sourceListenPort = sourceListenPort;
	}

	public int getRegistrationPort() {
		return registrationPort;
	}

	public void setRegistrationPort(int registrationPort) {
		this.registrationPort = registrationPort;
	}

	@Override
	public int setServerState(int port, int state) {
		 
		if (port == 7112){
			serverState = state;
		}
		return 0;
	}

	public int glueUrlExists( int port ) {
		 int result=0;
		 
		 String host;
	 
		 if (protocolID < MessageBoardFactoryProperties.protocolTCP ){
			
			 result=-1;
			 
			 host = restletBox.getRemoteHostAddress() ;
			  
			 if  (CheckIp.isReachable( host,port)>=0){
				 result = 0;
			 }else{
				 if ((host.contains("localhost")) || (host.contains("127.0.0.1"))){
					 result=-7;
				 }else{
					 result=-8;
				 }
			 }
		 }
		 
		 return result;
	}

	
}

 

class TcpBoxxer implements BoxDataIntf{

	TcpBox tcpBox ;
	
	BoxDataIntf tcpData ;
	
	FtpBoxPropertiesIntf fprops ; 
	DataDeliveryPackageIntf tcpPackageForSend ;
	
	PrintLog out = new PrintLog(2,true);
	
	public TcpBoxxer(){
		
	}
	
	public DataSlot getDataslot() {
		 
		return null;
	}

	 
	public DataSlot getBoxData(DataSlot dataslot) {
		 
		return null;
	}
	
	
}

class FtpBoxxer implements BoxDataIntf{
	
	FtpBox ftpbox ;
	
	BoxDataIntf ftpData ;
	
	FtpBoxPropertiesIntf fprops ; 
	DataDeliveryPackageIntf ftpPackageForSend ;
	
	PrintLog out = new PrintLog(2,true);
	
	public FtpBoxxer(){
		ftpbox = new FtpBox (this) ;
		ftpData = ftpbox.createFtpDataContainer() ;
		
	}
	
	public void getServerRunning(){

		fprops = ftpbox.createFtpBoxProperties() ;

		ftpPackageForSend = fprops.getFtpDeliveryPackage() ;
		
		_setFtpBoxProperties();
		
		ftpbox.initialize(fprops) ;
		
		ftpbox.startup() ;	
		
		ftpbox.selfTest();
	}
	
	
	private void _setFtpBoxProperties(){
		
		fprops.setListeningPorts( new int[] {7100,7200});
		
		fprops.setLoggingLevelAlias(0) ; // that is NOT equal to out.print(level,"") !!
										 // to block output, set it to 0 , 2 is rather verbose !
	}
	
	public void stop(){
		ftpbox.stop() ;
	}

	 
	protected void send(){
		
		String filename = "D:/dev/java/data/simple_test.xml";
		
		try {
			ftpPackageForSend.clearData();
			ftpPackageForSend.setStorFilename(filename) ;
		
			ftpPackageForSend.setDataAsInputStream( new FileInputStream( filename ) ) ;
		
			ftpbox.permamentLogin( ftpPackageForSend ) ;
			// ftpc.storeFile(filename, new FileInputStream(  filename));
		
			// use interface ftpPackageForSend for preparing package and delivering all-in-one
			ftpbox.send( ftpPackageForSend ) ;
			
		} catch (FileNotFoundException e) {
			 
			e.printStackTrace();
		}
	}

	 
	// ----------------------------------------------------
	// here we will receive the data from the physical layer
	
	 
	public DataSlot getDataslot() {
		 
		return null;
	}

	@Override
	public DataSlot getBoxData(DataSlot dataslot) {
		
		String str = new String(dataslot.getByteArray());
		
		out.print(2, "\ngetFtpMessageStr(obj) in main : \n"+str ) ;
		
		return null;	}
 
}
